 /*******************************************************************************
  * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Chris Gross chris.gross@us.ibm.com Bug 107443
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.ListenerList;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.action.ContributionItem;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.Geometry;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.graphics.Cursor;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IPersistable;
 import org.eclipse.ui.IPropertyListener;
 import org.eclipse.ui.IWorkbenchPartReference;
 import org.eclipse.ui.IWorkbenchPreferenceConstants;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.XMLMemento;
 import org.eclipse.ui.internal.StartupThreading.StartupRunnable;
 import org.eclipse.ui.internal.dnd.AbstractDropTarget;
 import org.eclipse.ui.internal.dnd.DragUtil;
 import org.eclipse.ui.internal.dnd.IDropTarget;
 import org.eclipse.ui.internal.dnd.SwtUtil;
 import org.eclipse.ui.internal.intro.IIntroConstants;
 import org.eclipse.ui.internal.layout.ITrimManager;
 import org.eclipse.ui.internal.layout.IWindowTrim;
 import org.eclipse.ui.internal.presentations.PresentablePart;
 import org.eclipse.ui.internal.presentations.PresentationFactoryUtil;
 import org.eclipse.ui.internal.presentations.PresentationSerializer;
 import org.eclipse.ui.internal.util.PrefUtil;
 import org.eclipse.ui.internal.util.Util;
 import org.eclipse.ui.presentations.AbstractPresentationFactory;
 import org.eclipse.ui.presentations.IPresentablePart;
 import org.eclipse.ui.presentations.IStackPresentationSite;
 import org.eclipse.ui.presentations.StackDropResult;
 import org.eclipse.ui.presentations.StackPresentation;

 /**
  * Implements the common behavior for stacks of Panes (ie: EditorStack and ViewStack)
  * This layout container has PartPanes as children and belongs to a PartSashContainer.
  *
  * @since 3.0
  */
 public abstract class PartStack extends LayoutPart implements ILayoutContainer {

     public static final int PROP_SELECTION = 0x42;
     
     private List children = new ArrayList (3);
     private boolean isActive = true;
     private ArrayList presentableParts = new ArrayList ();
     
     private Map properties = new HashMap ();
     
     protected int appearance = PresentationFactoryUtil.ROLE_VIEW;
     
     /**
      * Stores the last value passed to setSelection. If UI updates are being deferred,
      * this may be significantly different from the other current pointers. Once UI updates
      * are re-enabled, the stack will update the presentation selection to match the requested
      * current pointer.
      */
     private LayoutPart requestedCurrent;
     
     /**
      * Stores the current part for the stack. Whenever the outside world asks a PartStack
      * for the current part, this is what gets returned. This pointer is only updated after
      * the presentation selection has been restored and the stack has finished updating its
      * internal state. If the stack is still in the process of updating the presentation,
      * it will still point to the previous part until the presentation is up-to-date.
      */
     private LayoutPart current;
     
     /**
      * Stores the presentable part sent to the presentation. Whenever the presentation
      * asks for the current part, this is what gets returned. This is updated before sending
      * the part to the presentation, and it is not updated while UI updates are disabled.
      * When UI updates are enabled, the stack first makes presentationCurrent match
      * requestedCurrent. Once the presentation is displaying the correct part, the "current"
      * pointer on PartStack is updated.
      */
     private PresentablePart presentationCurrent;

     private boolean ignoreSelectionChanges = false;

     protected IMemento savedPresentationState = null;

     protected DefaultStackPresentationSite presentationSite = new DefaultStackPresentationSite() {

         public void close(IPresentablePart part) {
             PartStack.this.close(part);
         }

         public void close(IPresentablePart[] parts) {
             PartStack.this.close(parts);
         }

         public void dragStart(IPresentablePart beingDragged,
                 Point initialLocation, boolean keyboard) {
             PartStack.this.dragStart(beingDragged, initialLocation, keyboard);
         }

         public void dragStart(Point initialLocation, boolean keyboard) {
             PartStack.this.dragStart(null, initialLocation, keyboard);
         }

         public boolean isPartMoveable(IPresentablePart part) {
             return PartStack.this.isMoveable(part);
         }

         public void selectPart(IPresentablePart toSelect) {
             PartStack.this.presentationSelectionChanged(toSelect);
         }

         public boolean supportsState(int state) {
             return PartStack.this.supportsState(state);
         }

         public void setState(int newState) {
             PartStack.this.setState(newState);
         }

         public IPresentablePart getSelectedPart() {
             return PartStack.this.getSelectedPart();
         }

         public void addSystemActions(IMenuManager menuManager) {
             PartStack.this.addSystemActions(menuManager);
         }

         public boolean isStackMoveable() {
             return canMoveFolder();
         }
         
         public void flushLayout() {
             PartStack.this.flushLayout();
         }

         public IPresentablePart[] getPartList() {
             List parts = getPresentableParts();
             
             return (IPresentablePart[]) parts.toArray(new IPresentablePart[parts.size()]);
         }

         public String getProperty(String id) {
             return PartStack.this.getProperty(id);
         }
     };

     private static final class PartStackDropResult extends AbstractDropTarget {
         private PartPane pane;
         
         // Result of the presentation's dragOver method or null if we are stacking over the
 // client area of the pane.
 private StackDropResult dropResult;
         private PartStack stack;
         
         /**
          * Resets the target of this drop result (allows the same drop result object to be
          * reused)
          *
          * @param stack
          * @param pane
          * @param result result of the presentation's dragOver method, or null if we are
          * simply stacking anywhere.
          * @since 3.1
          */
         public void setTarget(PartStack stack, PartPane pane, StackDropResult result) {
             this.pane = pane;
             this.dropResult = result;
             this.stack = stack;
         }
         
         public void drop() {
             // If we're dragging a pane over itself do nothing
 //if (dropResult.getInsertionPoint() == pane.getPresentablePart()) { return; };

             Object cookie = null;
             if (dropResult != null) {
                 cookie = dropResult.getCookie();
             }

             // Handle cross window drops by opening a new editor
 if (pane instanceof EditorPane) {
                 if (pane.getWorkbenchWindow() != stack.getWorkbenchWindow()) {
                     EditorPane editor = (EditorPane) pane;
                     try {
                         IEditorInput input = editor.getEditorReference().getEditorInput();
                         
                         // Close the old editor and capture the actual closed state incase of a 'cancel'
 boolean editorClosed = editor.getPage().closeEditor(editor.getEditorReference(), true);
                         
                         // Only open open the new editor if the old one closed
 if (editorClosed)
                             stack.getPage().openEditor(input, editor.getEditorReference().getId());
                         return;
                     } catch (PartInitException e) {
                         e.printStackTrace();
                     }
                     
                 }
             }
             
             if (pane.getContainer() != stack) {
                 // Moving from another stack
 stack.derefPart(pane);
                 pane.reparent(stack.getParent());
                 stack.add(pane, cookie);
                 stack.setSelection(pane);
                 pane.setFocus();
             } else if (cookie != null) {
                 // Rearranging within this stack
 stack.getPresentation().movePart(stack.getPresentablePart(pane), cookie);
             }
         }

         public Cursor getCursor() {
             return DragCursors.getCursor(DragCursors.CENTER);
         }

         public Rectangle getSnapRectangle() {
             if (dropResult == null) {
                 return DragUtil.getDisplayBounds(stack.getControl());
             }
             return dropResult.getSnapRectangle();
         }
     }

     private static final PartStackDropResult dropResult = new PartStackDropResult();

     protected boolean isMinimized;

     private ListenerList listeners = new ListenerList();

     /**
      * Custom presentation factory to use for this stack, or null to
      * use the default
      */
     private AbstractPresentationFactory factory;

     private boolean smartZoomed = false;
     private boolean doingUnzoom = false;
             
     protected abstract boolean isMoveable(IPresentablePart part);

     protected abstract void addSystemActions(IMenuManager menuManager);

     protected abstract boolean supportsState(int newState);

     protected abstract boolean canMoveFolder();

     protected abstract void derefPart(LayoutPart toDeref);

     protected abstract boolean allowsDrop(PartPane part);

     protected static void appendToGroupIfPossible(IMenuManager m,
             String groupId, ContributionItem item) {
         try {
             m.appendToGroup(groupId, item);
         } catch (IllegalArgumentException e) {
             m.add(item);
         }
     }
     
     /**
      * Creates a new PartStack, given a constant determining which presentation to use
      *
      * @param appearance one of the PresentationFactoryUtil.ROLE_* constants
      */
     public PartStack(int appearance) {
         this(appearance, null);
     }
     
     /**
      * Creates a new part stack that uses the given custom presentation factory
      * @param appearance
      * @param factory custom factory to use (or null to use the default)
      */
     public PartStack(int appearance, AbstractPresentationFactory factory) {
         super("PartStack"); //$NON-NLS-1$

         this.appearance = appearance;
         this.factory = factory;
     }

     /**
      * Adds a property listener to this stack. The listener will receive a PROP_SELECTION
      * event whenever the result of getSelection changes
      *
      * @param listener
      */
     public void addListener(IPropertyListener listener) {
         listeners.add(listener);
     }
     
     public void removeListener(IPropertyListener listener) {
         listeners.remove(listener);
     }
     
     protected final boolean isStandalone() {
         return (appearance == PresentationFactoryUtil.ROLE_STANDALONE
              || appearance == PresentationFactoryUtil.ROLE_STANDALONE_NOTITLE);
     }
     
     /**
      * Returns the currently selected IPresentablePart, or null if none
      *
      * @return
      */
     protected IPresentablePart getSelectedPart() {
         return presentationCurrent;
     }

     protected IStackPresentationSite getPresentationSite() {
         return presentationSite;
     }

     /**
      * Tests the integrity of this object. Throws an exception if the object's state
      * is invalid. For use in test suites.
      */
     public void testInvariants() {
         Control focusControl = Display.getCurrent().getFocusControl();

         boolean currentFound = false;

         LayoutPart[] children = getChildren();

         for (int idx = 0; idx < children.length; idx++) {
             LayoutPart child = children[idx];

             // No null children allowed
 Assert.isNotNull(child,
                     "null children are not allowed in PartStack"); //$NON-NLS-1$

             // This object can only contain placeholders or PartPanes
 Assert.isTrue(child instanceof PartPlaceholder
                     || child instanceof PartPane,
                     "PartStack can only contain PartPlaceholders or PartPanes"); //$NON-NLS-1$

             // Ensure that all the PartPanes have an associated presentable part
 IPresentablePart part = getPresentablePart(child);
             if (child instanceof PartPane) {
                 Assert.isNotNull(part,
                         "All PartPanes must have a non-null IPresentablePart"); //$NON-NLS-1$
 }

             // Ensure that the child's backpointer points to this stack
 ILayoutContainer childContainer = child.getContainer();

             // Disable tests for placeholders -- PartPlaceholder backpointers don't
 // obey the usual rules -- they sometimes point to a container placeholder
 // for this stack instead of the real stack.
 if (!(child instanceof PartPlaceholder)) {

                 if (isDisposed()) {

                     // Currently, we allow null backpointers if the widgetry is disposed.
 // However, it is never valid for the child to have a parent other than
 // this object
 if (childContainer != null) {
                         Assert
                                 .isTrue(childContainer == this,
                                         "PartStack has a child that thinks it has a different parent"); //$NON-NLS-1$
 }
                 } else {
                     // If the widgetry exists, the child's backpointer must point to us
 Assert
                             .isTrue(childContainer == this,
                                     "PartStack has a child that thinks it has a different parent"); //$NON-NLS-1$

                     // If this child has focus, then ensure that it is selected and that we have
 // the active appearance.

                     if (SwtUtil.isChild(child.getControl(), focusControl)) {
                         Assert.isTrue(child == current,
                                 "The part with focus is not the selected part"); //$NON-NLS-1$
 // focus check commented out since it fails when focus workaround in LayoutPart.setVisible is not present
 // Assert.isTrue(getActive() == StackPresentation.AS_ACTIVE_FOCUS);
 }
                 }
             }

             // Ensure that "current" points to a valid child
 if (child == current) {
                 currentFound = true;
             }

             // Test the child's internal state
 child.testInvariants();
         }

         // If we have at least one child, ensure that the "current" pointer points to one of them
 if (!isDisposed() && getPresentableParts().size() > 0) {
             Assert.isTrue(currentFound);

             if (!isDisposed()) {
                 StackPresentation presentation = getPresentation();

                 // If the presentation controls have focus, ensure that we have the active appearance
 if (SwtUtil.isChild(presentation.getControl(), focusControl)) {
                     Assert
                             .isTrue(
                                     getActive() == StackPresentation.AS_ACTIVE_FOCUS,
                                     "The presentation has focus but does not have the active appearance"); //$NON-NLS-1$
 }
             }
         }
         
         // Check to that we're displaying the zoomed icon iff we're actually maximized
 Assert.isTrue((getState() == IStackPresentationSite.STATE_MAXIMIZED)
                 == (getContainer() != null && getContainer().childIsZoomed(this)));

     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.internal.LayoutPart#describeLayout(java.lang.StringBuffer)
      */
     public void describeLayout(StringBuffer buf) {
         int activeState = getActive();
         if (activeState == StackPresentation.AS_ACTIVE_FOCUS) {
             buf.append("active "); //$NON-NLS-1$
 } else if (activeState == StackPresentation.AS_ACTIVE_NOFOCUS) {
             buf.append("active_nofocus "); //$NON-NLS-1$
 }

         buf.append("("); //$NON-NLS-1$

         LayoutPart[] children = ((ILayoutContainer) this).getChildren();

         int visibleChildren = 0;

         for (int idx = 0; idx < children.length; idx++) {

             LayoutPart next = children[idx];
             if (!(next instanceof PartPlaceholder)) {
                 if (idx > 0) {
                     buf.append(", "); //$NON-NLS-1$
 }

                 if (next == requestedCurrent) {
                     buf.append("*"); //$NON-NLS-1$
 }

                 next.describeLayout(buf);

                 visibleChildren++;
             }
         }

         buf.append(")"); //$NON-NLS-1$
 }

     /**
      * See IVisualContainer#add
      */
     public void add(LayoutPart child) {
         add(child, null);
     }

     /**
      * Add a part at a particular position
      */
     protected void add(LayoutPart newChild, Object cookie) {
         children.add(newChild);
         
         // Fix for bug 78470:
 if(!(newChild.getContainer() instanceof ContainerPlaceholder)) {
             newChild.setContainer(this);
         }
         
         showPart(newChild, cookie);
     }

     public boolean allowsAdd(LayoutPart toAdd) {
         return !isStandalone();
     }
     
     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.ILayoutContainer#allowsAutoFocus()
      */
     public boolean allowsAutoFocus() {
         if (presentationSite.getState() == IStackPresentationSite.STATE_MINIMIZED) {
             return false;
         }

         return super.allowsAutoFocus();
     }

     /**
      * @param parts
      */
     protected void close(IPresentablePart[] parts) {
         for (int idx = 0; idx < parts.length; idx++) {
             IPresentablePart part = parts[idx];

             close(part);
         }
     }

     /**
      * @param part
      */
     protected void close(IPresentablePart part) {
         if (!presentationSite.isCloseable(part)) {
             return;
         }

         LayoutPart layoutPart = getPaneFor(part);

         if (layoutPart != null && layoutPart instanceof PartPane) {
             PartPane viewPane = (PartPane) layoutPart;

             viewPane.doHide();
         }
     }

     public boolean isDisposed() {
         return getPresentation() == null;
     }

     protected AbstractPresentationFactory getFactory() {
         
         if (factory != null) {
             return factory;
         }
         
         return ((WorkbenchWindow) getPage()
                 .getWorkbenchWindow()).getWindowConfigurer()
                 .getPresentationFactory();
     }
     
     public void createControl(Composite parent) {
         if (!isDisposed()) {
             return;
         }

         AbstractPresentationFactory factory = getFactory();

         PresentationSerializer serializer = new PresentationSerializer(
                 getPresentableParts());

         StackPresentation presentation = PresentationFactoryUtil
                 .createPresentation(factory, appearance, parent,
                         presentationSite, serializer, savedPresentationState);

         createControl(parent, presentation);
         getControl().moveBelow(null);
     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.internal.LayoutPart#getDropTarget(java.lang.Object, org.eclipse.swt.graphics.Point)
      */
     public IDropTarget getDropTarget(Object draggedObject, Point position) {

         if (!(draggedObject instanceof PartPane)) {
             return null;
         }

         final PartPane pane = (PartPane) draggedObject;
         if (isStandalone()
                 || !allowsDrop(pane)) {
             return null;
         }

         // Don't allow views to be dragged between windows
 boolean differentWindows = pane.getWorkbenchWindow() != getWorkbenchWindow();
         boolean editorDropOK = ((pane instanceof EditorPane) &&
                 pane.getWorkbenchWindow().getWorkbench() ==
                     getWorkbenchWindow().getWorkbench());
         if (differentWindows && !editorDropOK) {
             return null;
         }

         StackDropResult dropResult = getPresentation().dragOver(
                 getControl(), position);
         
         if (dropResult == null) {
             return null;
         }
         
         return createDropTarget(pane, dropResult);
     }
     
     public void setActive(boolean isActive) {
  
         this.isActive = isActive;
         // Add all visible children to the presentation
 Iterator iter = children.iterator();
         while (iter.hasNext()) {
             LayoutPart part = (LayoutPart) iter.next();
             
             part.setContainer(isActive ? this : null);
         }
         
         for (Iterator iterator = presentableParts.iterator(); iterator.hasNext();) {
             PresentablePart next = (PresentablePart) iterator.next();
             
             next.enableInputs(isActive);
             next.enableOutputs(isActive);
         }
     }
     
     public void createControl(Composite parent, StackPresentation presentation) {

         Assert.isTrue(isDisposed());

         if (presentationSite.getPresentation() != null) {
             return;
         }

         presentationSite.setPresentation(presentation);

         // Add all visible children to the presentation
 // Use a copy of the current set of children to avoid a ConcurrentModificationException
 // if a part is added to the same stack while iterating over the children (bug 78470)
 LayoutPart[] childParts = (LayoutPart[]) children.toArray(new LayoutPart[children.size()]);
         for (int i = 0; i < childParts.length; i++) {
             LayoutPart part = childParts[i];
             showPart(part, null);
         }
         
         if (savedPresentationState!=null) {
             PresentationSerializer serializer = new PresentationSerializer(
                     getPresentableParts());
             presentation.restoreState(serializer, savedPresentationState);
         }

         Control ctrl = getPresentation().getControl();

         ctrl.setData(this);

         // We should not have a placeholder selected once we've created the widgetry
 if (requestedCurrent instanceof PartPlaceholder) {
             requestedCurrent = null;
             updateContainerVisibleTab();
         }

         refreshPresentationSelection();
     }

     public IDropTarget createDropTarget(PartPane pane, StackDropResult result) {
         dropResult.setTarget(this, pane, result);
         return dropResult;
     }
     
     /**
      * Saves the current state of the presentation to savedPresentationState, if the
      * presentation exists.
      */
     protected void savePresentationState() {
         if (isDisposed()) {
             return;
         }

         {// Save the presentation's state before disposing it
 XMLMemento memento = XMLMemento
                     .createWriteRoot(IWorkbenchConstants.TAG_PRESENTATION);
             memento.putString(IWorkbenchConstants.TAG_ID, getFactory().getId());

             PresentationSerializer serializer = new PresentationSerializer(
                     getPresentableParts());

             getPresentation().saveState(serializer, memento);

             // Store the memento in savedPresentationState
 savedPresentationState = memento;
         }
     }

     /**
      * See LayoutPart#dispose
      */
     public void dispose() {

         if (isDisposed()) {
             return;
         }

         savePresentationState();

         presentationSite.dispose();
         
         for (Iterator iter = presentableParts.iterator(); iter.hasNext();) {
             PresentablePart part = (PresentablePart) iter.next();
             
             part.dispose();
         }
         presentableParts.clear();
         
         presentationCurrent = null;
         current = null;
         fireInternalPropertyChange(PROP_SELECTION);
     }

     public void findSashes(LayoutPart part, PartPane.Sashes sashes) {
         ILayoutContainer container = getContainer();

         if (container != null) {
             container.findSashes(this, sashes);
         }
     }

     /**
      * Gets the presentation bounds.
      */
     public Rectangle getBounds() {
         if (getPresentation() == null) {
             return new Rectangle(0, 0, 0, 0);
         }

         return getPresentation().getControl().getBounds();
     }

     /**
      * See IVisualContainer#getChildren
      */
     public LayoutPart[] getChildren() {
         return (LayoutPart[]) children.toArray(new LayoutPart[children.size()]);
     }

     public Control getControl() {
         StackPresentation presentation = getPresentation();

         if (presentation == null) {
             return null;
         }

         return presentation.getControl();
     }

     /**
      * Answer the number of children.
      */
     public int getItemCount() {
         if (isDisposed()) {
             return children.size();
         }
         return getPresentableParts().size();
     }
     
     /**
      * Returns the LayoutPart for the given IPresentablePart, or null if the given
      * IPresentablePart is not in this stack. Returns null if given a null argument.
      *
      * @param part to locate or null
      * @return
      */
     protected LayoutPart getPaneFor(IPresentablePart part) {
         if (part == null || !(part instanceof PresentablePart)) {
             return null;
         }

         return ((PresentablePart)part).getPane();
     }

     /**
      * Get the parent control.
      */
     public Composite getParent() {
         return getControl().getParent();
     }

     /**
      * Returns a list of IPresentablePart
      *
      * @return
      */
     public List getPresentableParts() {
         return presentableParts;
     }

     private PresentablePart getPresentablePart(LayoutPart pane) {
         for (Iterator iter = presentableParts.iterator(); iter.hasNext();) {
             PresentablePart part = (PresentablePart) iter.next();
             
             if (part.getPane() == pane) {
                 return part;
             }
         }
         
         return null;
     }
     
     protected StackPresentation getPresentation() {
         return presentationSite.getPresentation();
     }

     /**
      * Returns the visible child.
      * @return the currently visible part, or null if none
      */
     public PartPane getSelection() {
         if (current instanceof PartPane) {
             return (PartPane) current;
         }
         return null;
     }

     private void presentationSelectionChanged(IPresentablePart newSelection) {
         // Ignore selection changes that occur as a result of removing a part
 if (ignoreSelectionChanges) {
             return;
         }
         LayoutPart newPart = getPaneFor(newSelection);

         // This method should only be called on objects that are already in the layout
 Assert.isNotNull(newPart);

         if (newPart == requestedCurrent) {
             return;
         }

         setSelection(newPart);

         if (newPart != null) {
             newPart.setFocus();
         }

     }

     /**
      * See IVisualContainer#remove
      */
     public void remove(LayoutPart child) {
         PresentablePart presentablePart = getPresentablePart(child);

         // Need to remove it from the list of children before notifying the presentation
 // since it may setVisible(false) on the part, leading to a partHidden notification,
 // during which findView must not find the view being removed. See bug 60039.
 children.remove(child);

         StackPresentation presentation = getPresentation();

         if (presentablePart != null && presentation != null) {
             ignoreSelectionChanges = true;
             presentableParts .remove(presentablePart);
             presentation.removePart(presentablePart);
             presentablePart.dispose();
             ignoreSelectionChanges = false;
         }

         if (!isDisposed()) {
             child.setContainer(null);
         }

         if (child == requestedCurrent) {
             updateContainerVisibleTab();
         }
     }

     /**
      * Reparent a part. Also reparent visible children...
      */
     public void reparent(Composite newParent) {

         Control control = getControl();
         if ((control == null) || (control.getParent() == newParent) || !control.isReparentable()) {
             return;
         }

         super.reparent(newParent);

         Iterator iter = children.iterator();
         while (iter.hasNext()) {
             LayoutPart next = (LayoutPart) iter.next();
             next.reparent(newParent);
         }
     }

     /**
      * See IVisualContainer#replace
      */
     public void replace(LayoutPart oldChild, LayoutPart newChild) {
         int idx = children.indexOf(oldChild);
         int numPlaceholders = 0;
         //subtract the number of placeholders still existing in the list
 //before this one - they wont have parts.
 for (int i = 0; i < idx; i++) {
             if (children.get(i) instanceof PartPlaceholder) {
                 numPlaceholders++;
             }
         }
         Integer cookie = new Integer (idx - numPlaceholders);
         children.add(idx, newChild);

         showPart(newChild, cookie);

         if (oldChild == requestedCurrent && !(newChild instanceof PartPlaceholder)) {
             setSelection(newChild);
         }

         remove(oldChild);
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.ui.internal.LayoutPart#computePreferredSize(boolean, int, int, int)
      */
     public int computePreferredSize(boolean width, int availableParallel,
             int availablePerpendicular, int preferredParallel) {
         
         return getPresentation().computePreferredSize(width, availableParallel,
                 availablePerpendicular, preferredParallel);
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.ui.internal.LayoutPart#getSizeFlags(boolean)
      */
     public int getSizeFlags(boolean horizontal) {
         StackPresentation presentation = getPresentation();
         
         if (presentation != null) {
             return presentation.getSizeFlags(horizontal);
         }
         
         return 0;
     }
     
     /**
      * @see IPersistable
      */
     public IStatus restoreState(IMemento memento) {
         // Read the active tab.
 String activeTabID = memento
                 .getString(IWorkbenchConstants.TAG_ACTIVE_PAGE_ID);

         // Read the page elements.
 IMemento[] children = memento.getChildren(IWorkbenchConstants.TAG_PAGE);
         if (children != null) {
             // Loop through the page elements.
 for (int i = 0; i < children.length; i++) {
                 // Get the info details.
 IMemento childMem = children[i];
                 String partID = childMem
                         .getString(IWorkbenchConstants.TAG_CONTENT);

                 // Create the part.
 LayoutPart part = new PartPlaceholder(partID);
                 part.setContainer(this);
                 add(part);
                 //1FUN70C: ITPUI:WIN - Shouldn't set Container when not active
 //part.setContainer(this);
 if (partID.equals(activeTabID)) {
                     setSelection(part);
                     // Mark this as the active part.
 //current = part;
 }
             }
         }

         IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore();
         boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX);
         final Integer expanded = memento.getInteger(IWorkbenchConstants.TAG_EXPANDED);
         if (useNewMinMax && expanded != null) {
             StartupThreading.runWithoutExceptions(new StartupRunnable() {
                 public void runWithException() throws Throwable {
                     setState((expanded == null || expanded.intValue() != IStackPresentationSite.STATE_MINIMIZED) ? IStackPresentationSite.STATE_RESTORED
                             : IStackPresentationSite.STATE_MINIMIZED);
                 }
             });
         }
         else {
             setState((expanded == null || expanded.intValue() != IStackPresentationSite.STATE_MINIMIZED) ? IStackPresentationSite.STATE_RESTORED
                     : IStackPresentationSite.STATE_MINIMIZED);
         }

         Integer appearance = memento
                 .getInteger(IWorkbenchConstants.TAG_APPEARANCE);
         if (appearance != null) {
             this.appearance = appearance.intValue();
        }

        // Determine if the presentation has saved any info here
 savedPresentationState = null;
        IMemento[] presentationMementos = memento
                .getChildren(IWorkbenchConstants.TAG_PRESENTATION);

        for (int idx = 0; idx < presentationMementos.length; idx++) {
            IMemento child = presentationMementos[idx];

            String id = child.getString(IWorkbenchConstants.TAG_ID);

            if (Util.equals(id, getFactory().getId())) {
                savedPresentationState = child;
                break;
            }
        }

        IMemento propertiesState = memento.getChild(IWorkbenchConstants.TAG_PROPERTIES);
        if (propertiesState != null) {
            IMemento[] props = propertiesState.getChildren(IWorkbenchConstants.TAG_PROPERTY);
            for (int i = 0; i < props.length; i++) {
                properties.put(props[i].getID(), props[i].getTextData());
            }
        }
                
        
        return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
 }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#setVisible(boolean)
     */
    public void setVisible(boolean makeVisible) {
        Control ctrl = getControl();
        
        boolean useShortcut = makeVisible || !isActive;
        
        if (!SwtUtil.isDisposed(ctrl) && useShortcut) {
            if (makeVisible == ctrl.getVisible()) {
                return;
            }
        }
        
        if (makeVisible) {
            for (Iterator iterator = presentableParts.iterator(); iterator.hasNext();) {
                PresentablePart next = (PresentablePart) iterator.next();
                
                next.enableInputs(isActive);
                next.enableOutputs(isActive);
            }
        }
        
        super.setVisible(makeVisible);
        
        StackPresentation presentation = getPresentation();
        
        if (presentation != null) {
            presentation.setVisible(makeVisible);
        }
        
        if (!makeVisible) {
            for (Iterator iterator = presentableParts.iterator(); iterator.hasNext();) {
                PresentablePart next = (PresentablePart) iterator.next();
                
                next.enableInputs(false);
            }
        }
    }
    
    /**
     * @see IPersistable
     */
    public IStatus saveState(IMemento memento) {

        // Save the active tab.
 if (requestedCurrent != null) {
            memento.putString(IWorkbenchConstants.TAG_ACTIVE_PAGE_ID, requestedCurrent
                    .getCompoundId());
        }

        // Write out the presentable parts (in order)
 Set cachedIds = new HashSet ();
        Iterator ppIter = getPresentableParts().iterator();
        while (ppIter.hasNext()) {
            PresentablePart presPart = (PresentablePart) ppIter.next();

            IMemento childMem = memento
                    .createChild(IWorkbenchConstants.TAG_PAGE);
            PartPane part = presPart.getPane();
            String tabText = part.getPartReference().getPartName();

            childMem.putString(IWorkbenchConstants.TAG_LABEL, tabText);
            childMem.putString(IWorkbenchConstants.TAG_CONTENT, presPart.getPane().getPlaceHolderId());
            
            // Cache the id so we don't write it out later
 cachedIds.add(presPart.getPane().getPlaceHolderId());
        }

        Iterator iter = children.iterator();
        while (iter.hasNext()) {
            LayoutPart next = (LayoutPart) iter.next();

            PartPane part = null;
            if (next instanceof PartPane) {
                // Have we already written it out?
 if (cachedIds.contains(((PartPane)next).getPlaceHolderId()))
                    continue;
                
                part = (PartPane)next;
            }

            IMemento childMem = memento
                    .createChild(IWorkbenchConstants.TAG_PAGE);

            String tabText = "LabelNotFound"; //$NON-NLS-1$
 if (part != null) {
                tabText = part.getPartReference().getPartName();
            }
            childMem.putString(IWorkbenchConstants.TAG_LABEL, tabText);
            childMem.putString(IWorkbenchConstants.TAG_CONTENT, next
                    .getCompoundId());
        }

        IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore();
        boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX);
        if (useNewMinMax) {
            memento.putInteger(IWorkbenchConstants.TAG_EXPANDED, presentationSite.getState());
        }
        else {
            memento
            .putInteger(
                    IWorkbenchConstants.TAG_EXPANDED,
                    (presentationSite.getState() == IStackPresentationSite.STATE_MINIMIZED) ? IStackPresentationSite.STATE_MINIMIZED
                            : IStackPresentationSite.STATE_RESTORED);
        }

        memento.putInteger(IWorkbenchConstants.TAG_APPEARANCE, appearance);

        savePresentationState();

        if (savedPresentationState != null) {
            IMemento presentationState = memento
                    .createChild(IWorkbenchConstants.TAG_PRESENTATION);
            presentationState.putMemento(savedPresentationState);
        }
        
        if (!properties.isEmpty()) {
            IMemento propertiesState = memento.createChild(IWorkbenchConstants.TAG_PROPERTIES);
            Set ids = properties.keySet();
            for (Iterator iterator = ids.iterator(); iterator.hasNext();) {
                String id = (String )iterator.next();
                
                if (properties.get(id) == null) continue;
                
                IMemento prop = propertiesState.createChild(IWorkbenchConstants.TAG_PROPERTY, id);
                prop.putTextData((String )properties.get(id));
            }
        }
        

        return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
 }

    protected WorkbenchPage getPage() {
        WorkbenchWindow window = (WorkbenchWindow) getWorkbenchWindow();

        if (window == null) {
            return null;
        }

        return (WorkbenchPage) window.getActivePage();
    }

    /**
     * Set the active appearence on the tab folder.
     *
     * @param active
     */
    public void setActive(int activeState) {

        if (activeState == StackPresentation.AS_ACTIVE_FOCUS && isMinimized) {
            setMinimized(false);
        }

        presentationSite.setActive(activeState);
    }

    public int getActive() {
        return presentationSite.getActive();
    }

    /**
     * Sets the presentation bounds.
     */
    public void setBounds(Rectangle r) {
        
        if (getPresentation() != null) {
            getPresentation().setBounds(r);
        }
    }

    public void setSelection(LayoutPart part) {
        if (part == requestedCurrent) {
            return;
        }

        requestedCurrent = part;
        
        refreshPresentationSelection();
    }

    /**
     * Subclasses should override this method to update the enablement state of their
     * actions
     */
    protected abstract void updateActions(PresentablePart current);

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#handleDeferredEvents()
     */
    protected void handleDeferredEvents() {
        super.handleDeferredEvents();
        
        refreshPresentationSelection();
    }
    
    private void refreshPresentationSelection() {
        // If deferring UI updates, exit.
 if (isDeferred()) {
            return;
        }
        
        // If the presentation is already displaying the desired part, then there's nothing
 // to do.
 if (current == requestedCurrent) {
            return;
        }

        StackPresentation presentation = getPresentation();
        if (presentation != null) {
        
            presentationCurrent = getPresentablePart(requestedCurrent);
            
            if (!isDisposed()) {
                updateActions(presentationCurrent);
            }
            
            if (presentationCurrent != null && presentation != null) {
                requestedCurrent.createControl(getParent());
                if (requestedCurrent.getControl().getParent() != getControl()
                        .getParent()) {
                    requestedCurrent.reparent(getControl().getParent());
                }

               
                presentation.selectPart(presentationCurrent);
                
             }
        
            // Update the return value of getVisiblePart
 current = requestedCurrent;
            fireInternalPropertyChange(PROP_SELECTION);
        }
    }

    public int getState() {
        return presentationSite.getState();
    }

    /**
     * Sets the minimized state for this stack. The part may call this method to
     * minimize or restore itself. The minimized state only affects the view
     * when unzoomed in the 3.0 presentation (in 3.3 it's handled by the
     * ViewStack directly and works as expected).
     */
    public void setMinimized(boolean minimized) {
        if (minimized != isMinimized) {
            isMinimized = minimized;

            refreshPresentationState();
        }
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.ILayoutContainer#obscuredByZoom(org.eclipse.ui.internal.LayoutPart)
     */
    public boolean childObscuredByZoom(LayoutPart toTest) {
        return isObscuredByZoom();
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#requestZoom(org.eclipse.ui.internal.LayoutPart)
     */
    public void childRequestZoomIn(LayoutPart toZoom) {
        super.childRequestZoomIn(toZoom);
        
        requestZoomIn();
    }
    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.LayoutPart#requestZoomOut()
     */
    public void childRequestZoomOut() {
        super.childRequestZoomOut();
        
        requestZoomOut();
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.ILayoutContainer#isZoomed(org.eclipse.ui.internal.LayoutPart)
     */
    public boolean childIsZoomed(LayoutPart toTest) {
        return isZoomed();
    }
    
    /**
     * This is a hack that allows us to preserve the old
     * min/max behavior for the stack containing the IntroPart.
     * This is required to have the initial Intro (Welcome)
     * pane to show correctly but will induce strange
     * effects should a user re-locate the part to
     * stacks other that its initial one...
     *
     * @return true if the stack contains the intro
     * as a ViewPane (not if it's only a placeholder)
     */
    private boolean isIntroInStack() {
        LayoutPart[] kids = getChildren();
        for (int i = 0; i < kids.length; i++) {
            if (kids[i] instanceof ViewPane) {
                ViewPane vp = (ViewPane) kids[i];
                if (vp.getID().equals(IIntroConstants.INTRO_VIEW_ID))
                    return true;
            }
        }
        return false;
    }

    private void smartZoom() {
        WorkbenchWindow wbw = (WorkbenchWindow) getPage().getWorkbenchWindow();
        if (wbw == null || wbw.getShell() == null)
            return;

        Perspective perspective = getPage().getActivePerspective();
        FastViewManager fvm = perspective.getFastViewManager();

        fvm.deferUpdates(true);

        // Cache the layout bounds
 perspective.getPresentation().updateBoundsMap();
        
        LayoutPart[] children = perspective.getPresentation().getLayout().getChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i] != this) {
                if (children[i] instanceof ViewStack) {
                    ((ViewStack) children[i]).setMinimized(true);
                    ViewStackTrimToolBar vstb = fvm
                            .getViewStackTrimToolbar(children[i]
                                    .getID());
                    vstb.setRestoreOnUnzoom(true);
                }
                else if (children[i] instanceof EditorSashContainer && !(this instanceof EditorStack)) {
                    perspective.setEditorAreaState(IStackPresentationSite.STATE_MINIMIZED);
                    perspective.setEditorAreaRestoreOnUnzoom(true);
                }
            }
        }

        // If the editor area has changed state tell the perspective
 if (this instanceof EditorStack)
            perspective.setEditorAreaState(IStackPresentationSite.STATE_MAXIMIZED);

        // Clear the boundsMap
 perspective.getPresentation().resetBoundsMap();
        
        // We're done batching...
 fvm.deferUpdates(false);
        
        perspective.getPresentation().setMaximizedStack(this);
        smartZoomed = true;
    }

    protected void smartUnzoom() {
        // Prevent recursion through 'setMinimized'
 if (doingUnzoom)
            return;
        doingUnzoom = true;
        
        WorkbenchWindow wbw = (WorkbenchWindow) getPage().getWorkbenchWindow();
        if (wbw == null || wbw.getShell() == null)
            return;

        ITrimManager tbm = wbw.getTrimManager();
        Perspective perspective = getPage().getActivePerspective();
        FastViewManager fvm = perspective.getFastViewManager();

        ILayoutContainer root = getContainer();

        // We go up one more level when maximizing an editor stack
 // so that we 'zoom' the editor area
 boolean restoringEditorArea = false;
        if (root instanceof EditorSashContainer) {
            root = ((EditorSashContainer) root).getContainer();
            restoringEditorArea = true;
        }

        // This is a compound operation
 fvm.deferUpdates(true);
        
        LayoutPart[] children = root.getChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i] != this) {
                IWindowTrim trim = tbm.getTrim(children[i].getID());
                if (trim == null)
                    continue;

                if (trim instanceof ViewStackTrimToolBar) {
                    ViewStackTrimToolBar vstb = (ViewStackTrimToolBar) trim;
                    if (vstb.restoreOnUnzoom()
                            && children[i] instanceof ContainerPlaceholder) {
                        // In the current presentation its a
 // container placeholder
 ViewStack realStack = (ViewStack) ((ContainerPlaceholder) children[i])
                                .getRealContainer();
                        realStack.setMinimized(false);

                        vstb.setRestoreOnUnzoom(false);
                    }
                } else if (trim instanceof EditorAreaTrimToolBar) {
                    if (perspective.getEditorAreaRestoreOnUnzoom())
                    perspective.setEditorAreaState(IStackPresentationSite.STATE_RESTORED);
                }
            }
        }

        // If the editor area has changed state tell the perspective
 if (restoringEditorArea)
            perspective.setEditorAreaState(IStackPresentationSite.STATE_RESTORED);

        perspective.getPresentation().setMaximizedStack(null);
        
        fvm.deferUpdates(false);
        smartZoomed = false;
        
        doingUnzoom = false;
    }
    
    protected void setState(final int newState) {
        final int oldState = presentationSite.getState();
        if (!supportsState(newState) || newState == oldState) {
            return;
        }

        final WorkbenchWindow wbw = (WorkbenchWindow) getPage().getWorkbenchWindow();
        if (wbw == null || wbw.getShell() == null || wbw.getActiveWorkbenchPage() == null)
            return;

        WorkbenchPage page = wbw.getActiveWorkbenchPage();
        if (page == null)
            return;
        
        boolean useNewMinMax = Perspective.useNewMinMax(page.getActivePerspective());

        // we have to fiddle with the zoom behavior to satisfy Intro req's
 // by usning the old zoom behavior for its stack
 if (newState == IStackPresentationSite.STATE_MAXIMIZED)
            useNewMinMax = useNewMinMax && !isIntroInStack();
        else if (newState == IStackPresentationSite.STATE_RESTORED) {
            PartStack maxStack = page.getActivePerspective().getPresentation().getMaximizedStack();
            useNewMinMax = useNewMinMax && maxStack == this;
        }

        if (useNewMinMax) {
            StartupThreading.runWithoutExceptions(new StartupRunnable() {
                public void runWithException() throws Throwable {
                    wbw.getPageComposite().setRedraw(false);
                    try {
                        if (newState == IStackPresentationSite.STATE_MAXIMIZED) {
                            smartZoom();
                        } else if (oldState == IStackPresentationSite.STATE_MAXIMIZED) {
                            smartUnzoom();
                        }
                        
                        if (newState == IStackPresentationSite.STATE_MINIMIZED) {
                            setMinimized(true);
                        }
                    } finally {
                        wbw.getPageComposite().setRedraw(true);

                        // Force a redraw (fixes Mac refresh)
 wbw.getShell().redraw();
                    }

                    setPresentationState(newState);
                }
            });
        } else {
            boolean minimized = (newState == IStackPresentationSite.STATE_MINIMIZED);
            setMinimized(minimized);

            if (newState == IStackPresentationSite.STATE_MAXIMIZED) {
                requestZoomIn();
            } else if (oldState == IStackPresentationSite.STATE_MAXIMIZED) {
                requestZoomOut();
                
                if (newState == IStackPresentationSite.STATE_MINIMIZED)
                    setMinimized(true);
            }
        }
    }
    

    /**
     * Called by the workbench page to notify this part that it has been zoomed or unzoomed.
     * The PartStack should not call this method itself -- it must request zoom changes by
     * talking to the WorkbenchPage.
     */
    public void setZoomed(boolean isZoomed) {
        
        super.setZoomed(isZoomed);
        
        LayoutPart[] children = getChildren();
        
        for (int i = 0; i < children.length; i++) {
            LayoutPart next = children[i];
            
            next.setZoomed(isZoomed);
        }
        
        refreshPresentationState();
    }
    
    public boolean isZoomed() {
        ILayoutContainer container = getContainer();
        
        if (container != null) {
            return container.childIsZoomed(this);
        }
        
        return false;
    }
    
    protected void refreshPresentationState() {
        if (isZoomed() || smartZoomed) {
            presentationSite.setPresentationState(IStackPresentationSite.STATE_MAXIMIZED);
        } else {
            
            boolean wasMinimized = (presentationSite.getState() == IStackPresentationSite.STATE_MINIMIZED);
            
            if (isMinimized) {
                presentationSite.setPresentationState(IStackPresentationSite.STATE_MINIMIZED);
            } else {
                presentationSite.setPresentationState(IStackPresentationSite.STATE_RESTORED);
            }
            
            if (isMinimized != wasMinimized) {
                flushLayout();
                
                if (isMinimized) {
                    WorkbenchPage page = getPage();
    
                    if (page != null) {
                        page.refreshActiveView();
                    }
                }
            }
        }
    }

    /**
     * Makes the given part visible in the presentation.
     * @param part the part to add to the stack
     * @param cookie other information
     */
    private void showPart(LayoutPart part, Object cookie) {

        if (isDisposed()) {
            return;
        }
        
        if ((part instanceof PartPlaceholder)) {
            part.setContainer(this);
            return;
        }

        if (!(part instanceof PartPane)) {
            WorkbenchPlugin.log(NLS.bind(
                    WorkbenchMessages.PartStack_incorrectPartInFolder, part
                            .getID()));
            return;
        }
        
        PartPane pane = (PartPane)part;
        
        PresentablePart presentablePart = new PresentablePart(pane, getControl().getParent());
        presentableParts.add(presentablePart);
        
        if (isActive) {
            part.setContainer(this);
        }
        
        presentationSite.getPresentation().addPart(presentablePart, cookie);

        if (requestedCurrent == null) {
            setSelection(part);
        }
        
        if (childObscuredByZoom(part)) {
            presentablePart.enableInputs(false);
        }
    }

    /**
     * Update the container to show the correct visible tab based on the
     * activation list.
     */
    private void updateContainerVisibleTab() {
        LayoutPart[] parts = getChildren();

        if (parts.length < 1) {
            setSelection(null);
            return;
        }

        PartPane selPart = null;
        int topIndex = 0;
        WorkbenchPage page = getPage();

        if (page != null) {
            IWorkbenchPartReference sortedPartsArray[] = page.getSortedParts();
            List sortedParts = Arrays.asList(sortedPartsArray);
            for (int i = 0; i < parts.length; i++) {
                if (parts[i] instanceof PartPane) {
                    IWorkbenchPartReference part = ((PartPane) parts[i])
                            .getPartReference();
                    int index = sortedParts.indexOf(part);
                    if (index >= topIndex) {
                        topIndex = index;
                        selPart = (PartPane) parts[i];
                    }
                }
            }

        }

        if (selPart == null) {
            List presentableParts = getPresentableParts();
            if (presentableParts.size() != 0) {
                IPresentablePart part = (IPresentablePart) getPresentableParts()
                        .get(0);

                selPart = (PartPane) getPaneFor(part);
            }
        }

        setSelection(selPart);
    }

    /**
     *
     */
    public void showSystemMenu() {
        getPresentation().showSystemMenu();
    }

    public void showPaneMenu() {
        getPresentation().showPaneMenu();
    }

    public void showPartList() {
        getPresentation().showPartList();
    }
    
    public Control[] getTabList(LayoutPart part) {
        if (part != null) {
            IPresentablePart presentablePart = getPresentablePart(part);
            StackPresentation presentation = getPresentation();

            if (presentablePart != null && presentation != null) {
                return presentation.getTabList(presentablePart);
            }
        }

        return new Control[0];
    }

    /**
     *
     * @param beingDragged
     * @param initialLocation
     * @param keyboard
     */
    private void dragStart(IPresentablePart beingDragged, Point initialLocation,
            boolean keyboard) {
        if (beingDragged == null) {
            paneDragStart((LayoutPart)null, initialLocation, keyboard);
        } else {
            if (presentationSite.isPartMoveable(beingDragged)) {
                LayoutPart pane = getPaneFor(beingDragged);

                if (pane != null) {
                    paneDragStart(pane, initialLocation, keyboard);
                }
            }
        }
    }
    
    public void paneDragStart(LayoutPart pane, Point initialLocation,
            boolean keyboard) {
        if (pane == null) {
            if (canMoveFolder()) {
                
                if (presentationSite.getState() == IStackPresentationSite.STATE_MAXIMIZED) {
                    // Calculate where the initial location was BEFORE the 'restore'...as a percentage
 Rectangle bounds = Geometry.toDisplay(getParent(), getPresentation().getControl().getBounds());
                    float xpct = (initialLocation.x - bounds.x) / (float)(bounds.width);
                    float ypct = (initialLocation.y - bounds.y) / (float)(bounds.height);

                    setState(IStackPresentationSite.STATE_RESTORED);

                    // Now, adjust the initial location to be within the bounds of the restored rect
 bounds = Geometry.toDisplay(getParent(), getPresentation().getControl().getBounds());
                    initialLocation.x = (int) (bounds.x + (xpct * bounds.width));
                    initialLocation.y = (int) (bounds.y + (ypct * bounds.height));
                }
    
                DragUtil.performDrag(PartStack.this, Geometry
                        .toDisplay(getParent(), getPresentation().getControl()
                                .getBounds()), initialLocation, !keyboard);
            }
        } else {

            if (presentationSite.getState() == IStackPresentationSite.STATE_MAXIMIZED) {
                // Calculate where the initial location was BEFORE the 'restore'...as a percentage
 Rectangle bounds = Geometry.toDisplay(getParent(), getPresentation().getControl().getBounds());
                float xpct = (initialLocation.x - bounds.x) / (float)(bounds.width);
                float ypct = (initialLocation.y - bounds.y) / (float)(bounds.height);
                
                presentationSite.setState(IStackPresentationSite.STATE_RESTORED);

                // Now, adjust the initial location to be within the bounds of the restored rect
 // See bug 100908
 bounds = Geometry.toDisplay(getParent(), getPresentation().getControl().getBounds());
                initialLocation.x = (int) (bounds.x + (xpct * bounds.width));
                initialLocation.y = (int) (bounds.y + (ypct * bounds.height));
            }
    
            DragUtil.performDrag(pane, Geometry.toDisplay(getParent(),
                    getPresentation().getControl().getBounds()),
                    initialLocation, !keyboard);
        }
    }

    /**
     * @return Returns the savedPresentationState.
     */
    public IMemento getSavedPresentationState() {
        return savedPresentationState;
    }
    
    private void fireInternalPropertyChange(int id) {
        Object listeners[] = this.listeners.getListeners();
        for (int i = 0; i < listeners.length; i++) {
            ((IPropertyListener) listeners[i]).propertyChanged(this, id);
        }
    }
    
    // TrimStack Support

    /**
     * Explicitly sets the presentation state. This is used by the
     * new min/max code to force the CTabFolder to show the proper
     * state without going through the 'setState' code (which causes
     * nasty side-effects.
     * @param newState The state to set the presentation to
     */
    public void setPresentationState(int newState) {
        presentationSite.setPresentationState(newState);
    }

    //
 // Support for passing perspective layout properties to the presentation

    
    public String getProperty(String id) {
        return (String )properties.get(id);
    }
    
    public void setProperty(String id, String value) {
        if (value==null) {
            properties.remove(id);
        } else {
            properties.put(id, value);
        }
    }
    
    /**
     * Copies all appearance related data from this stack to the given stack.
     */
    public void copyAppearanceProperties(PartStack copyTo) {
        copyTo.appearance = this.appearance;
        if (!properties.isEmpty()) {
            Set ids = properties.keySet();
            for (Iterator iterator = ids.iterator(); iterator.hasNext();) {
                String id = (String )iterator.next();
                copyTo.setProperty(id, (String )properties.get(id));
            }
        }
    }
}

